package com.neuronbit.businessflow.flow.parser;

import com.neuronbit.businessflow.flow.clazz.*;
import com.neuronbit.businessflow.flow.project.Project;
import com.neuronbit.businessflow.intellij.model.Call;
import com.neuronbit.businessflow.intellij.model.FormattedMethodString;
import com.neuronbit.businessflow.intellij.model.Type;
import com.neuronbit.javaparser.resolution.types.ResolvedType;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * The interface Callable node.
 */
public interface CallableNode {


    /**
     * Gets type.
     *
     * @return the type
     */
    default Type getType() {
        return getCallable().getType();
    }

    /**
     * Gets callable.
     *
     * @return the callable
     */
    Call getCallable();

    /**
     * Gets method.
     *
     * @return the method
     */
    default String getMethodName() {
        FormattedMethodString method = getCallable().getMethod();
        if (null != method) {
            return method.getName();
        } else {
            return null;
        }
    }

    /**
     * Gets param count.
     *
     * @return the param count
     */
    default int getParamCount() {
        return getCallable().getParams().size();
    }

    /**
     * Sets param type.
     *
     * @param index the index
     * @param type  the type
     */
    default void setParamType(String index, Type type) {
        getCallable().getParams().get(index).setType(type);
    }

    /**
     * Sets param name.
     *
     * @param index the index
     * @param name  the name
     */
    default void setParamName(String index, String name) {
        getCallable().getParams().get(index).setName(name);
    }

    /**
     * Resolve method.
     *
     * @param project the class finder
     */
    default void resolveMethod(Project project) {
        String type = getType().getQualifiedName();
        String methodName = getMethodName();
        int paramCount = getParamCount();
        if (StringUtils.isAnyBlank(type, methodName)) {
            return;
        }
        try {
            ClassInfo clazz = project.find(type);
            if (null == clazz) {
                throw new ClassNotFoundException(type + " is not find");
            }
            List<MethodInfo> methods = findMethodByName(clazz, methodName, paramCount);
            if (!methods.isEmpty()) {
                //TODO shunyun 2024/4/8: 多个 method 如何处理
                if (methods.size() > 1) {
                    throw new NoSuchMethodException("more than one method found with " + methodName + " in class " + type + " with " + paramCount + " parameters.");
                }
                MethodInfo method = methods.get(0);
                setOutput(new Output("output", "", method.getReturnType()));
                ParameterInfo[] parameters = method.getParameters();
                for (int i = 0; i < method.getParameterCount(); i++) {
                    setParamType(i+"", SourceType.of(parameters[i].getType()));
                    setParamName(i+"", parameters[i].getName());
                }
                setExceptionTypes(method.getExceptionTypes());
            } else {
                throw new NoSuchMethodException("can not find method " + methodName + " in class " + type + " with " + paramCount + " parameters.");
            }
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Sets exception types.
     *
     * @param exceptionTypes the exception types
     */
    void setExceptionTypes(ExceptionTypeInfo[] exceptionTypes);

    /**
     * Get exception types exception type info [ ].
     *
     * @return the exception type info [ ]
     */
    ExceptionTypeInfo[] getExceptionTypes();

    /**
     * Find method by name list.
     *
     * @param clazz      the clazz
     * @param name       the name
     * @param paramCount the param count
     * @return the list
     */
    default List<MethodInfo> findMethodByName(ClassInfo clazz, String name, int paramCount) {
        //TODO shunyun 2024/8/15: 用方法签名进行过滤
        MethodInfo[] methods = clazz.getMethods();
        return Arrays.stream(methods).filter(m -> m.getName().equals(name) && m.getParameterCount() == paramCount).collect(Collectors.toList());
    }

    /**
     * Sets output.
     *
     * @param output the output
     */
    void setOutput(Output output);
}
